package com.leenmvc.core.utils.socket;

import cn.hutool.core.io.BufferUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.util.StrUtil;
import cn.hutool.socket.nio.NioClient;
import com.alibaba.fastjson.JSONObject;
import com.leenmvc.core.enums.SocketMessageType;
import com.leenmvc.core.utils.DateUtils;
import com.leenmvc.core.utils.thread.ThreadPoolUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * @author leen
 */
public class SocketClient {

    private final static Logger logger = LoggerFactory.getLogger(SocketClient.class);

    private String ip;

    private int port;

    private NioClient client;

    /**
     * clientID是很重要的一个字段，他描述了当前SocketClient的唯一标识码
     * 别的客户端如果想要发送给这个客户端信息的话需要知道指定客户端的clientId
     *
     */
    private String clientId;

    private volatile AtomicBoolean finesh = new AtomicBoolean(false);

    private StringBuffer data = new StringBuffer("");

    private LinkedBlockingDeque<SocketMessage> queue = new LinkedBlockingDeque();

    /**
     * 存储主题处理方法的HashMap
     *
     */
    private Map<String, Method> themeHandlerMap = new HashMap<>();
    /**
     * 主题处理器
     */
    private SocketTheme socketTheme;
    /**
     * 客户端构造方法
     *
     * @param ip
     * @param port
     * @param clientId
     * @param socketTheme
     */
    public SocketClient(String ip, int port, String clientId, SocketTheme socketTheme) {
        this.ip = ip;
        this.port = port;
        this.clientId = clientId;
        buildThemeHandlerMap(socketTheme);
        ThreadPoolUtil.run(new SocketDispatch(clientId));
    }

    private void buildThemeHandlerMap(SocketTheme socketTheme) {
        this.socketTheme = socketTheme;
        Class<? extends SocketTheme> cls = socketTheme.getClass();
        Method[] declaredMethods = cls.getMethods();
        for(Method method: declaredMethods) {
            themeHandlerMap.put(method.getName(), method);
        }
    }


    /**
     * 实际执行任务的线程
     */
    class SocketClientReal implements Runnable {
        public void run() {
            Thread.currentThread().setName("SocketClientReal-" + clientId + "-" + DateUtils.format(new Date(), "yyyyMMddHHmmss"));
            while (!monitor()) {
                if (finesh.get()) {
                    logger.info("检测到当前客户端已经被关闭，本次客户端结束！");
                    return;
                }
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    logger.info(e.getMessage(), e);
                }
            }
            client.setChannelHandler((sc) -> {
                socketChannelHandler(sc);
            });
            client.listen();
            register();
        }

        /**
         * 对通道数据进行处理
         *
         * @param sc
         * @throws Exception
         */
        private void socketChannelHandler(SocketChannel sc) throws Exception {
            ByteBuffer readBuffer = ByteBuffer.allocate(1024 * 5);
            //从channel读数据到缓冲区
            int readBytes = 0;
            try {
                readBytes = sc.read(readBuffer);
            } catch (IOException e) {
                logger.info(e.getMessage(), e);
                sc.close();
                client.close();
                synchronized (finesh) {
                    finesh.notifyAll();
                }
            }

            if (readBytes > 0) {
                readBuffer.flip();
                byte[] bytes = new byte[readBuffer.remaining()];
                readBuffer.get(bytes);
                String body = StrUtil.utf8Str(bytes);
                data.append(body);
                dataHandler(sc);
            } else if (readBytes < 0) {
                sc.close();
            }
        }
    }


    /**
     * 拆包方法，并将拆好的包放置在队列中准备被消费
     *
     * @param sc
     */
    private void dataHandler(SocketChannel sc) {
        int index = 0;
        while (true) {
            int start = index = data.indexOf(SocketConstant.START_STR, index);
            if (start == -1) { // 终止条件为找不到指定占位标识符
                data.setLength(0);
                break;
            }
            int end = index = data.indexOf(SocketConstant.END_STR, index);
            if (end == -1) { // 断包了
                int newLength = data.length() - start; // 新长度
                CharSequence sequence = data.subSequence(start, start + newLength);
                data.setLength(newLength);
                data.replace(0, newLength, sequence.toString());
                break;
            }
            CharSequence charSequence = data.subSequence(start + SocketConstant.START_STR.length(), end);
            String socketMessageStr = charSequence.toString();
            SocketMessage socketMessage = JSONObject.parseObject(socketMessageStr, SocketMessage.class);
            if(socketTheme != null) {
                try {
                    themeHandlerMap.get(socketMessage.getTheme()).invoke(socketTheme, socketMessage, this, sc);
                } catch (IllegalAccessException e) {
                    logger.error(e.getMessage(), e);
                } catch (InvocationTargetException e) {
                    logger.error(e.getMessage(), e);
                } catch (NullPointerException e) {
                    logger.error(e.getMessage(), e);
                }
            }
        }
    }

    /**
     * 关闭与这个SocketClient所有有关系的线程和任务
     */
    public void shutdown() {
        synchronized (finesh) {
            finesh.compareAndSet(false, true);
            finesh.notifyAll();
            SocketMessage socketMessage = new SocketMessage();
            socketMessage.setTheme(SocketConstant.SHUTDOWN_THEME);
            socketMessage.setType(SocketMessageType.SEND_TO_ONE);
            socketMessage.setContent("主动关闭SocketClient！");
            socketMessage.setTo(clientId);
            write(socketMessage);
        }
    }

    /**
     * 当前Socket的调度器（当前socket客户端的守护线程）
     */
    class SocketDispatch implements Runnable {

        private String clientId;

        public SocketDispatch(String clientId) {
            this.clientId = clientId;
        }

        public void run() {
            Thread.currentThread().setName("SocketDispatch-" + clientId + "-" + DateUtils.format(new Date(), "yyyyMMddHHmmss"));
            a:
            while (true) {
                synchronized (finesh) {
                    if (finesh.get()) {
                        logger.info("用户主动关闭了当前SocketClient, 本次客户端结束...");
                        break a;
                    }
                    ThreadPoolUtil.run(new SocketClientReal());
                    try {
                        finesh.wait();
                    } catch (InterruptedException e) {
                        logger.error(e.getMessage(), e);
                    }
                }
            }
            logger.info("用户主动关闭了当前SocketClient, 本次客户端结束了...");
        }
    }

    /**
     * 向服务器注册
     */
    private void register() {
        SocketMessage socketMessage = new SocketMessage();
        socketMessage.setType(SocketMessageType.SEND_TO_ALL);
        socketMessage.setTheme(SocketConstant.REGISTER_INFO);
        socketMessage.setContent("客户端{" + clientId + "}注册成功，准备接收数据。");
        write(socketMessage);
    }

    public void write(SocketMessage message) {
        if (message.getType() == null || message.getContent() == null) {
            logger.info("消息数据的类型和内容不能为空");
            return;
        }
        message.setClientId(clientId);
        message.setFrom(clientId);
        client.write(BufferUtil.createUtf8(SocketConstant.START_STR + JSONObject.toJSONString(message) + SocketConstant.END_STR));
    }

    /**
     * 获取SocketMessage
     *
     * @return
     */
    public SocketMessage getSocketMessage() {
        try {
            return this.queue.take();
        } catch (InterruptedException e) {
            logger.info(e.getMessage(), e);
        }
        return null;
    }

    /**
     * 判断给定IP和端口的服务器是否启动可连接
     *
     * @return
     */
    private boolean monitor() {
        try {
            client = new NioClient(ip, port);
        } catch (IORuntimeException e) {
            logger.error(e.getMessage(), e);
            return false;
        }
        return true;
    }

    public NioClient getClient() {
        return client;
    }
}